﻿using UnityEngine;
using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;

public class Watchdog
{
    public class LogMessage
    {
        public enum Type
        {
            INFO,
            WARNING,
            ERROR,
            EXCEPTION
        }

        public Type type;
        public string created;
        public string message;
        public string tag;

        private Exception _e;
        public Exception e
        {
            get
            {
                return _e;
            }
            set
            {
                if(value != null)
                {
                    message = value.Message;
                    _e = value;
                }
            }
        }

        public override string ToString()
        {
            string color = "white";
            switch (this.type)
            {
                case Watchdog.LogMessage.Type.INFO: color = "white"; break;
                case Watchdog.LogMessage.Type.WARNING: color = "yellow"; break;
                case Watchdog.LogMessage.Type.ERROR: color = "#ff6699ff"; break;
                case Watchdog.LogMessage.Type.EXCEPTION: color = "#ff6699ff"; break;
            }

            return string.Format("{0}\t<color={1}>[{2}]{3} {4}</color>\r\n", created, color, this.type.ToString(), ((this.tag != "") ? "<" + this.tag + ">" : ""), this.message);
        }
    }

    private const int MAX_LOG_COUNT = 200;
    private const int MAX_CHARACTER_LENGTH = 15000;

    public static List<LogMessage> logs;
    public static List<string> tags;

    private static List<string> tagFilters;
    private static List<Watchdog.LogMessage.Type> typeFilters;


    public static string FiltersInfo()
    {
        string str = "Tags:";
        if (tagFilters != null) tagFilters.ForEach(t => str += t + " ");

        str += " Types:";
        if (typeFilters != null) typeFilters.ForEach(t => str += t + " ");

        return str;
    }

    public static void Init()
    {

        logs = new List<LogMessage>();
        tags = new List<string>();

        // Register callback
        Application.logMessageReceived += Watchdog.DebugLogCallback;

        // Log success info
        Watchdog.Log("Watchdog", "**Initialized**");
    }

    public static void AddTagFilter(string tagFilter)
    {
        if (tagFilters == null) tagFilters = new List<string>();
        tagFilters.Add(tagFilter);
    }

    public static void AddTypeFilter(Watchdog.LogMessage.Type type)
    {
        if (typeFilters == null) typeFilters = new List<Watchdog.LogMessage.Type>();
        typeFilters.Add(type);
    }

    public static bool CheckHasFilter(LogMessage logMessage)
    {
        bool hasTagFilter = tagFilters != null && tagFilters.Contains(logMessage.tag);
        bool hasTypeFilter = typeFilters != null && typeFilters.Contains(logMessage.type);

        return hasTagFilter || hasTypeFilter;
    }

    private static bool handlerSwith = true;
    public static void DebugLogCallback(string logString, string stackTrace, LogType type)
    {

        if (!handlerSwith) return;

#if DEBUG_CONSOLE
        switch (type)
        {
            case LogType.Log:
                AddToLogs("Debug.Log", logString + "\n<color=grey>" + stackTrace + "</color>", LogMessage.Type.INFO);
                break;
            case LogType.Warning:
                AddToLogs("Debug.Warning", "<color=yellow>" + logString + "</color>\n<color=grey>" + stackTrace + "</color>", LogMessage.Type.WARNING);
                break;
            case LogType.Error:
                AddToLogs("Debug.Error", "<color=red>" + logString + "</color>\n<color=grey>" + stackTrace + "</color>", LogMessage.Type.ERROR);
                break;
            case LogType.Assert:
                AddToLogs("Debug.Assert", "<color=aqua>" + logString + "</color>\n<color=grey>" + stackTrace + "</color>", LogMessage.Type.INFO);
                break;
        }
#endif

    }

    public const string DEFAULT = "default";
    public static void Log(string message)
    {
        Log(DEFAULT, message);
    }

    public static void Log(string tag, string message)
    {

        LogMessage logMessage = AddToLogs(tag, message, LogMessage.Type.INFO);

#if UNITY_EDITOR

        InvokeWithoutLogHandler(() =>
        {
            if(!CheckHasFilter(logMessage))
                Debug.Log(string.Format("{0}{1}", string.IsNullOrEmpty(tag) ? "" : "[" + tag + "] ", message));
        });
#endif
    }

    public static void LogWarning(string tag, string message)
    {

        LogMessage logMessage = AddToLogs(tag, message, LogMessage.Type.WARNING);

#if UNITY_EDITOR
      
        InvokeWithoutLogHandler(() =>
        {
            if (!CheckHasFilter(logMessage))
                Debug.LogWarning(string.Format("{0}{1}", string.IsNullOrEmpty(tag) ? "" : "[" + tag + "] ", message));
        });
#endif

    }

    public static void LogError(string tag, string message)
    {

        LogMessage logMessage = AddToLogs(tag, message, LogMessage.Type.ERROR);

#if UNITY_EDITOR
       
        InvokeWithoutLogHandler(() =>
        {
            if (!CheckHasFilter(logMessage))
                Debug.LogError(string.Format("{0}{1}", string.IsNullOrEmpty(tag) ? "" : "[" + tag + "] ", message));
        });
#endif

    }

    public static void LogException(string tag, string str)
    {
        LogException(tag, new Exception(str));
    }

    public static void LogException(string tag, Exception e)
    {

        LogMessage logMessage = AddToLogs(tag, "", LogMessage.Type.EXCEPTION, e);

#if UNITY_EDITOR

        InvokeWithoutLogHandler(() =>
        {
            if (!CheckHasFilter(logMessage))
                Debug.LogException(e);
        });
#endif
    }

    private static LogMessage AddToLogs(string tag, string message, LogMessage.Type type = LogMessage.Type.INFO, Exception e = null)
    {
        LogMessage logMessage = null;

#if DEBUG_CONSOLE

        logMessage = new LogMessage()
        {
            message = message,
            type = type,
            e = e,
            tag = tag,
            created = System.DateTime.Now.ToString("yyyy.MM.dd.HH.mm.ss.fff"),
        };

        // Add logs
        logs.Add(logMessage);
        
        // Check capacity
        if(logs.Count > MAX_LOG_COUNT && logs.Count != 0)
        {
            logs.RemoveAt(0);
        }

#endif

        return logMessage;
    }

    private static void InvokeWithoutLogHandler(Action action)
    {
        handlerSwith = false;
        if(action != null) action();
        handlerSwith = true;
    }

    public static string GetString()
    {
        // Prepare pending list
        List<LogMessage> pendingList = logs.FindAll((log) =>
        {
            // Check all filters
            bool hasFilter = CheckHasFilter(log);
            return !hasFilter;
        });

        // Prepare pending string
        string pendingStr = "";
        for(int i = pendingList.Count - 1;i >= 0;i --)
        {
            if (pendingStr.Length >= MAX_CHARACTER_LENGTH) break;
            pendingStr += pendingList[i].ToString();
        }

        return pendingStr;
    }

    public static void Clear()
    {
        logs.Clear();
    }
}